package libce

import (
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"time"

	"github.com/go-redis/redis"
	"github.com/json-iterator/go"

	"gitee.com/goframe/lt-collector/libs/utils"
)

var (
	json  = jsoniter.ConfigCompatibleWithStandardLibrary
	Cache CEInterface

	ErrCacheType = errors.New("缓存类型错误")
)

func SetCache(ce CEInterface) {
	Cache = ce
}

func DeleteCacheByKey(model interface{}, key string) {
	switch ce := Cache.(type) {
	case *MemoryCache:
		k := fmt.Sprintf("%s_%s", utils.ObjectName(model), key)
		ce.Delete(k)
	case *Redis:
		k := fmt.Sprintf("%s", utils.ObjectName(model))
		f := fmt.Sprintf("%s", key)
		ce.mu.Lock()
		ce.Client().HDel(k, f)
		ce.mu.Unlock()
	}
}

func CacheBindModelByKey(model interface{}, key string, fn func() error) (err error) {
	switch ce := Cache.(type) {
	case *MemoryCache:
		k := fmt.Sprintf("%s_%s", utils.ObjectName(model), key)
		val, has := ce.Get(k)
		if has {
			model = val
			return
		}
		if err = fn(); err != nil {
			return
		}
		ce.Set(k, model, -1)
	case *Redis:
		k := fmt.Sprintf("%s", utils.ObjectName(model))
		f := fmt.Sprintf("%s", key)
		ce.mu.Lock()
		defer ce.mu.Unlock()
		val, er := ce.Client().HGet(k, f).Result()
		if er == nil {
			err = json.UnmarshalFromString(val, model)
			return
		}
		if er == redis.Nil {
			if err = fn(); err != nil {
				return
			}
			str, er := json.MarshalToString(model)
			if er != nil {
				return er
			}
			err = ce.Client().HSet(k, f, str).Err()
			return
		}
		err = er
	default:
		return ErrCacheType
	}
	return
}

func CacheBindModelById(model interface{}, id int64, fn func() error) error {
	return CacheBindModelByKey(model, fmt.Sprintf("id_%d", id), fn)
}

type (
	CEInterface interface {
		// Add an item to the cache, replacing any existing item. If the duration is 0
		// (DefaultExpiration), the cache's default expiration time is used. If it is -1
		// (NoExpiration), the item never expires.
		Set(k string, x interface{}, d time.Duration)
		// Get an item from the cache. Returns the item or nil, and a bool indicating
		// whether the key was found.
		Get(k string) (interface{}, bool)
		// Increment an item of type int, int8, int16, int32, int64, uintptr, uint,
		// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the
		// item's value is not an integer, if it was not found, or if it is not
		// possible to increment it by n. To retrieve the incremented value, use one
		// of the specialized methods.
		Increment(k string, n int64) error
		// Decrement an item of type int, int8, int16, int32, int64, uintptr, uint,
		// uint8, uint32, or uint64, float32 or float64 by n. Returns an error if the
		// item's value is not an integer, if it was not found, or if it is not
		// possible to decrement it by n. To retrieve the decremented value, use one
		// of the specialized methods.
		Decrement(k string, n int64) error
		// Delete an item from the cache. Does nothing if the key is not in the cache.
		Delete(k string)
		// Delete all items from the cache.
		Flush()

		RememberWarp(rememberFunc RememberFunc, op RememberOption) RememberFunc
	}

	RememberOption struct {
		Key    string
		Expire time.Duration

		//BindBean must is pointer
		BindBean interface{}
	}
)

type RememberFunc func() (value interface{}, _e error)

func (rf RememberFunc) MustGetValue() interface{} {
	val, err := rf()
	if err != nil {
		panic(err)
	}
	return val
}

type argInt []int

func (a argInt) Get(i int, args ...int) (r int) {
	if i >= 0 && i < len(a) {
		r = a[i]
	} else if len(args) > 0 {
		r = args[0]
	}
	return
}

func ToStr(value interface{}, args ...int) (s string) {
	switch v := value.(type) {
	case bool:
		s = strconv.FormatBool(v)
	case float32:
		s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32))
	case float64:
		s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64))
	case int:
		s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
	case int8:
		s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
	case int16:
		s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
	case int32:
		s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
	case int64:
		s = strconv.FormatInt(v, argInt(args).Get(0, 10))
	case uint:
		s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
	case uint8:
		s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
	case uint16:
		s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
	case uint32:
		s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
	case uint64:
		s = strconv.FormatUint(v, argInt(args).Get(0, 10))
	case string:
		s = v
	case []byte:
		s = string(v)
	default:
		str, err := json.MarshalToString(value)
		if err != nil {
			panic(err)
		}
		return str
	}
	return s
}

type StrTo string

func (f StrTo) Exist() bool {
	return string(f) != string(0x1E)
}
func (f StrTo) String() string {
	if f.Exist() {
		return string(f)
	}
	return ""
}
func (f StrTo) Convert(bind interface{}) (err error) {
	var bindVal reflect.Value
	typ := reflect.TypeOf(bind)
	if typ.Kind() == reflect.Ptr {
		typ = typ.Elem()
		bindVal = reflect.ValueOf(bind).Elem()
	} else {
		return errors.New("cannot reflect from non-pointer bind")
	}
	switch typ.Kind() {
	case reflect.Bool:
		b, e := strconv.ParseBool(f.String())
		if e != nil {
			return e
		}
		bindVal.SetBool(b)
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		b, e := strconv.ParseInt(f.String(), 10, 64)
		if e != nil {
			return e
		}
		bindVal.SetInt(b)
	case reflect.Float32, reflect.Float64:
		b, e := strconv.ParseFloat(f.String(), 64)
		if e != nil {
			return e
		}
		bindVal.SetFloat(b)
	case reflect.String:
		bindVal.SetString(f.String())
	default:
		err = json.UnmarshalFromString(f.String(), bind)
	}
	return
}
